package org.budo.accesslog.servlet.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import org.budo.accesslog.entity.AccesslogEntity;
import org.budo.jdbc.dao.impl.BudoJdbcDaoImpl;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.mvcs.Mvcs;
import org.budo.support.service.batch.buffered.AbstractBufferedBatchService;
import org.budo.time.Time;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * @author limingwei
 */
@Slf4j
@Getter
@Setter
public class BudoAccesslogServletFilter extends AbstractBufferedBatchService<AccesslogEntity> implements Filter {
    private DataSource dataSource;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        long start = System.currentTimeMillis();

        Throwable error = null;
        try {
            this.doFilterInternal((HttpServletRequest) request, (HttpServletResponse) response, chain);
        } catch (Throwable e) {
            error = e; // 有异常
        }

        long end = System.currentTimeMillis();
        int timeCost = (int) (end - start);

        AccesslogEntity accesslogEntity = this.newAccesslogEntity(request, error, timeCost);
        this.addToBuffer(accesslogEntity);

        // 如果有异常，抛出
        this.throwError(error);
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
    }

    @Override
    protected void flashBuffer(List<AccesslogEntity> bufferCopy) {
        if (null == bufferCopy || bufferCopy.isEmpty()) {
            return;
        }

        if (null == this.getDataSource()) {
            log.error("#60 dataSource is null, this=" + this);
            return;
        }

        String sql = " INSERT INTO t_accesslog ( `request_uri`, `type`, `remote_addr`, `created_at`, `time_cost`, `handler`, `error`, `server_name`, `user_agent` ) VALUES ";
        List<Object> parameters = new ArrayList<Object>();

        int size = bufferCopy.size();
        for (int i = 0; i < size; i++) {
            AccesslogEntity each = bufferCopy.get(i);

            if (i > 0) {
                sql += " , ";
            }

            sql += " ( ?, ?, ?, ?, ?, ?, ?, ?, ? ) ";

            parameters.add(each.getRequestUri());
            parameters.add(each.getType());
            parameters.add(each.getRemoteAddr());
            parameters.add(each.getCreatedAt());
            parameters.add(each.getTimeCost());
            parameters.add(each.getHandler());
            parameters.add(StringUtil.cutLeft(each.getError(), 250));
            parameters.add(each.getServerName());
            parameters.add(each.getUserAgent());
        }

        BudoJdbcDaoImpl jdbcDao = new BudoJdbcDaoImpl(this.getDataSource());
        jdbcDao.executeUpdate(sql, parameters.toArray(new Object[parameters.size()]));
    }

    protected AccesslogEntity newAccesslogEntity(ServletRequest request, Throwable error, int timeCost) {
        AccesslogEntity accesslogEntity = new AccesslogEntity();
        accesslogEntity.setRequestUri(Mvcs.getRequestURI(request));
        accesslogEntity.setType(Mvcs.getMethod(request));
        accesslogEntity.setRemoteAddr(Mvcs.getRemoteAddr(request));
        accesslogEntity.setCreatedAt(Time.now().toTimestamp());

        accesslogEntity.setServerName(request.getServerName());
        accesslogEntity.setUserAgent(Mvcs.getUserAgent(request));

        accesslogEntity.setTimeCost(timeCost);

        Object handler = Mvcs.getHandler();
        accesslogEntity.setHandler(null == handler ? null : handler.toString());

        accesslogEntity.setError(null == error ? null : error.toString());
        return accesslogEntity;
    }

    private void throwError(Throwable error) throws IOException, ServletException {
        if (null != error) {
            if (error instanceof IOException) {
                throw (IOException) error;
            }

            if (error instanceof ServletException) {
                throw (ServletException) error;
            }

            throw new RuntimeException(error);
        }
    }

    @Override
    public void destroy() {
        log.info("#37 destroy, this=" + this);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("#28 init, this=" + this);
    }
}